//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "UI/Types/Types.h"
#include "UI/Types/WidgetLayout.h"

namespace Engine
{
namespace UI
{

	//
	// Arrange Helper
	//

	struct ArrangeHelper
	{
	// types
	public:
		typedef static_array< WidgetLayout, ELayout::_COUNT >	layout_array_t;


	// variables
	private:
		vec2			_viewportAspect;
		vec2			_aspect;
		vec2			_screenSizeInPixels;
		vec2			_screenSizeInDips;
		vec2			_scale;			// 1 / designResolution
		vec2			_bias;
		vec2			_pixelScale;	// scale for measure ABSOLUTE_IN_PIXELS
		vec2			_dipScale;		// scale for measure ABSOLUTE_IN_DIPS
		float			_designDips;
		ELayout::type	_layout;
		EMeasure::type	_measure;		// default measure type


	// methods
	public:
		ArrangeHelper () : _designDips(1.0f), _layout(ELayout::DEFAULT), _measure(EMeasure::RELATIVE)
		{}


		void Setup (const Graphics::ViewportPtr &viewport,
					float designDips, const vec2 &designResolution,
					const rect &viewRect, EMeasure::type measure)
		{
			_viewportAspect		= viewport->GetResCorrectAspect();

			_screenSizeInPixels	= viewport->Size().To<vec2>();
			_screenSizeInDips	= viewport->SizeInDips();
			_designDips			= designDips;
			_bias				= viewRect.LeftBottom();
			_layout				= viewport->IsHorizontal() ? ELayout::HORIZONTAL : ELayout::VERTICAL;
			_measure			= measure;

			// set vertical as default layout
			vec2	res			= designResolution.y > designResolution.x ?
									designResolution : designResolution.yx();
			res					= _layout == ELayout::VERTICAL ?
									res : res.yx();

			vec2	aspect		= ResCorrectionAspect( res );
			aspect				= viewport->GetResCorrectAspect() / aspect;

			_aspect				= aspect / aspect.Max();	// clamp to 0..1 interval
			_scale				= viewRect.GetSize() / res;

			_pixelScale			= _scale * res / _screenSizeInPixels;
			_dipScale			= _scale * designDips / _screenSizeInDips;
		}


		ELayout::type	GetLayout () const
		{
			return _layout;
		}


		WidgetLayout &	GetLayout (layout_array_t &layouts) const
		{
			if ( layouts[ _layout ].enabled )
				return layouts[ _layout ];

			// return default
			return layouts[ ELayout::DEFAULT ];
		}


		vec2 const & GetCorrectionAspect () const 
		{
			return _aspect;
		}


		bool IsVisibleForLayout (ELayout::bits mask) const
		{
			return EnumCmp( mask, ELayout::bits(1 << _layout) );
		}


		bool IsAutoSize (const WidgetLayout &layout) const
		{
			return IsZero( layout.size ) and EnumNoCmp( layout.dock, EDock::FILL );
		}


		void CalcFontHeight (OUT float &scale, EMeasure::type measure, float height) const
		{
			measure = ( measure == EMeasure::AUTO ? _measure : measure );

			switch ( measure )
			{
				case EMeasure::ABSOLUTE_IN_DIPS :
					scale = height * ( _layout == ELayout::VERTICAL ? _dipScale.x : _dipScale.y );
					break;

				case EMeasure::ABSOLUTE_IN_PIXELS :
					scale = height * ( _layout == ELayout::VERTICAL ? _pixelScale.x : _pixelScale.y );
					break;

				case EMeasure::RELATIVE_PROPORTIONAL :
				case EMeasure::RELATIVE :
					scale = height * ( _layout == ELayout::VERTICAL ? _scale.x : _scale.y );
					break;
			}
		}


		void Arrange (OUT rect &region, const WidgetLayout &layout,
					  const rect &parentRegion, INOUT rect &parentDockRegion) const
		{
			const EMeasure::type	measure = ( layout.measure == EMeasure::AUTO ? _measure : layout.measure );

			if ( EnumNoCmp( layout.dock, EDock::FILL ) and IsZero( layout.size ) )
			{
				WARNING( "invalid layout parameters" );

				TransformPos( region.LeftBottom(), layout.measure, layout.pos );
				region.RightTop()   = parentRegion.RightTop();
			}

			// dock
			if ( layout.dock != EDock::NONE )
			{
				rect	margin;
				TransformSize( margin.LeftBottom(), measure, layout.margin.LeftBottom() );
				TransformSize( margin.RightTop(),   measure, layout.margin.RightTop() );

				vec2	size;
				TransformSize( size, measure, layout.size );

				// dock FILL
				region.LeftBottom() = parentDockRegion.LeftBottom() + margin.LeftBottom();
				region.RightTop()   = parentDockRegion.RightTop()   - margin.RightTop();
				
				if ( EnumNoCmp( layout.dock, EDock::FILL ) )
				{
					switch ( layout.dock )
					{
						case EDock::LEFT	: parentDockRegion.left   = region.right  = Min( region.right,  region.left   + size.x );	break;
						case EDock::RIGHT	: parentDockRegion.right  = region.left   = Max( region.left,   region.right  - size.x );	break;
						case EDock::BOTTOM	: parentDockRegion.bottom = region.top    = Min( region.top,    region.bottom + size.y );	break;
						case EDock::TOP		: parentDockRegion.top    = region.bottom = Max( region.bottom, region.top    - size.y );	break;
					}
				}
				else
					parentDockRegion = rect();
			}
			else
			{
				vec2 &	pos	= region.LeftBottom();
				TransformPos(  pos,  measure, layout.pos );

				// align
				if ( layout.align != EAlign::NONE )
				{
					rect	margin;
					TransformSize( margin.LeftBottom(), measure, layout.margin.LeftBottom() );
					TransformSize( margin.RightTop(),   measure, layout.margin.RightTop() );
				
					vec2	size;
					TransformSize( size, measure, layout.size );

					switch ( layout.align & EAlign::_HORIZONTAL )
					{
						case EAlign::LEFT		:	pos.x = parentRegion.left + margin.left;			break;
						case EAlign::RIGHT		:	pos.x = parentRegion.right - size.x - margin.right;	break;
						case EAlign::CENTER_X	:	pos.x = parentRegion.GetCenter().x - size.x * 0.5f - margin.Width();	break;
					}

					switch ( layout.align & EAlign::_VERTICAL )
					{
						case EAlign::BOTTOM		:	pos.y = parentRegion.bottom + margin.bottom;		break;
						case EAlign::TOP		:	pos.y = parentRegion.top - size.y - margin.top;		break;
						case EAlign::CENTER_Y	:	pos.y = parentRegion.GetCenter().y - size.y * 0.5f - margin.Height();	break;
					}
				}

				// pos, size
				switch ( measure )
				{
					case EMeasure::ABSOLUTE_IN_DIPS :
						region.SetSize( layout.size * _dipScale );
						break;

					case EMeasure::ABSOLUTE_IN_PIXELS :
						region.SetSize( layout.size * _pixelScale );
						break;

					case EMeasure::RELATIVE :
						region.SetSize( layout.size * _scale );
						break;

					case EMeasure::RELATIVE_PROPORTIONAL :
						region.SetSize( layout.size * _scale );
						_Align( region, layout.measureAlign );
						break;
				}
			}
		}


		void PlaceInRect (OUT rect &region, const vec2 &size, EAlign::type align,
						  const rect &parentRegion, const rect &margin) const
		{
			vec2 &	pos	= region.LeftBottom();

			switch ( align & EAlign::_HORIZONTAL )
			{
				case EAlign::LEFT		:	pos.x = parentRegion.left + margin.left;			break;
				case EAlign::RIGHT		:	pos.x = parentRegion.right - size.x - margin.right;	break;
				case EAlign::CENTER_X	:	pos.x = parentRegion.GetCenter().x - size.x * 0.5f - margin.Width();	break;
			}

			switch ( align & EAlign::_VERTICAL )
			{
				case EAlign::BOTTOM		:	pos.y = parentRegion.bottom + margin.bottom;		break;
				case EAlign::TOP		:	pos.y = parentRegion.top - size.y - margin.top;		break;
				case EAlign::CENTER_Y	:	pos.y = parentRegion.GetCenter().y - size.y * 0.5f - margin.Height();	break;
			}

			region.SetSize( size );
		}


		void ProportionalImageInRegion (OUT rect &region, const vec2 &imgSizeInPx, EAlign::type align,
										const rect &parentRegion) const
		{
			vec2	size		= parentRegion.GetSize();
			vec2	size_in_px	= size / _scale / _aspect;
			float	reg_aspect	= size_in_px.x / size_in_px.y;
			float	img_aspect	= imgSizeInPx.x / imgSizeInPx.y;
			float	aspect		= reg_aspect / img_aspect;

			if ( aspect > 1.0f )
				size.x /= aspect;
			else
				size.y *= aspect;

			PlaceInRect( region, size, align, parentRegion, rect() );
		}


		void TransformPos (OUT vec2 &result, EMeasure::type measure, const vec2 &coord) const
		{
			measure = ( measure == EMeasure::AUTO ? _measure : measure );

			switch ( measure )
			{
				case EMeasure::ABSOLUTE_IN_DIPS :
					result = coord * _dipScale + _bias;
					break;

				case EMeasure::ABSOLUTE_IN_PIXELS :
					result = coord * _pixelScale + _bias;
					break;

				case EMeasure::RELATIVE :
				case EMeasure::RELATIVE_PROPORTIONAL :
					result = coord * _scale + _bias;
					break;
			}
		}


		void TransformSize (OUT vec2 &result, EMeasure::type measure, const vec2 &size) const
		{
			measure = ( measure == EMeasure::AUTO ? _measure : measure );

			switch ( measure )
			{
				case EMeasure::ABSOLUTE_IN_DIPS :
					result = size * _dipScale;
					break;

				case EMeasure::ABSOLUTE_IN_PIXELS :
					result = size * _pixelScale;
					break;

				case EMeasure::RELATIVE :
					result = size * _scale;
					break;

				case EMeasure::RELATIVE_PROPORTIONAL :
					result = size * _scale * _aspect;
					break;
			}
		}


		void Transform (OUT rect &result, EMeasure::type measure, EAlign::type measureAlign, const rect &region) const
		{
			measure = ( measure == EMeasure::AUTO ? _measure : measure );

			switch ( measure )
			{
				case EMeasure::ABSOLUTE_IN_DIPS :
					result.LeftBottom() = region.LeftBottom() * _dipScale + _bias;
					result.SetSize( region.GetSize() * _dipScale );
					break;

				case EMeasure::ABSOLUTE_IN_PIXELS :
					result.LeftBottom() = region.LeftBottom() * _pixelScale + _bias;
					result.SetSize( region.GetSize() * _pixelScale );
					break;

				case EMeasure::RELATIVE :
					result.LeftBottom() = region.LeftBottom() * _scale + _bias;
					result.SetSize( region.GetSize() * _scale );
					break;

				case EMeasure::RELATIVE_PROPORTIONAL :
					result.LeftBottom() = region.LeftBottom() * _scale;
					result.SetSize( region.GetSize() * _scale * _aspect );
					_Align( result, measureAlign );
					break;
			}
		}


		void UnTransformPos (OUT vec2 &result, EMeasure::type measure, const vec2 &coord) const
		{
			measure = ( measure == EMeasure::AUTO ? _measure : measure );

			switch ( measure )
			{
				case EMeasure::ABSOLUTE_IN_DIPS :
					result = (coord - _bias) / _dipScale;
					break;

				case EMeasure::ABSOLUTE_IN_PIXELS :
					result = (coord - _bias) / _pixelScale;
					break;

				case EMeasure::RELATIVE :
				case EMeasure::RELATIVE_PROPORTIONAL :
					result = (coord - _bias) / _scale;
					break;
			}
		}


		void UnTransformSize (OUT vec2 &result, EMeasure::type measure, const vec2 &size) const
		{
			measure = ( measure == EMeasure::AUTO ? _measure : measure );

			switch ( measure )
			{
				case EMeasure::ABSOLUTE_IN_DIPS :
					result = size / _dipScale;
					break;

				case EMeasure::ABSOLUTE_IN_PIXELS :
					result = size / _pixelScale;
					break;

				case EMeasure::RELATIVE :
					result = size / _scale;
					break;

				case EMeasure::RELATIVE_PROPORTIONAL :
					result = Clamp( size / _scale / _aspect, vec2(), _screenSizeInPixels );
					break;
			}
		}


	private:
		void _Align (INOUT rect &region, EAlign::type align) const
		{
			const rect	tmp	 = region;
			const vec2	size = region.GetSize() * _aspect;

			switch ( align & EAlign::_HORIZONTAL )
			{
				case EAlign::LEFT		:	region.right = tmp.left + size.x;	break;
				case EAlign::RIGHT		:	region.left  = tmp.right - size.x; 	break;
				case EAlign::CENTER_X	:
					region.left  = tmp.GetCenter().x - size.x * 0.5f;
					region.right = region.left + size.x;
					break;
			}

			switch ( align & EAlign::_VERTICAL )
			{
				case EAlign::BOTTOM		:	region.top    = tmp.bottom + size.y;	break;
				case EAlign::TOP		:	region.bottom = tmp.top - size.y;		break;
				case EAlign::CENTER_Y	:
					region.bottom = tmp.GetCenter().y - size.y * 0.5f;
					region.top    = region.bottom + size.y;
					break;
			}
		}
	};


}	// UI
}	// Engine